
#include "qlreader.h"

#include <stdlib.h>
#include <string.h>

/*
 * represents context of the reader who performs lexical analysis
 */
struct ReaderContext
{
    /*
     * allocation context
     */
    AllocContext *      alloc_ctx;

    /*
     * file stream from which reader gets symbols
     */
    FILE *              stream;

    /*
     * the name of the stream
     */
    const char *        stream_name;

    /*
     * default namespace non-qualified id's belongs to
     */
    Id *                default_ns;

    /*
     * position in the stream
     */
    int                 line;
    int                 pos;

    /*
     * represents an error handler,
     * who dumps an information about parsing.
     * can be NULL
     */
    ErrResult           last_error;

    /*
     * this is for internal use only
     * last character which shall be returned
     */
    int                 ungetc;

    /*
     * this is for internal use only
     */
    jmp_buf             err_ret;

    /*
     * this is for internal use only
     * represents a helper list stack when parsing lists
     */
    List *              list_stack;

    /*
     * this is for internal user only
     * represents a buffer for string/identifiers array
     */
    char *              str_buf;

    /*
     * max size of an identifier/string within the buffer str_buf
     * not including zero symbol at the end of string
     * by default it is QL_DEFAULT_MAX_STR_SIZE
     */
    size_t              max_str_size;

    /*
     * user-defined tag
     */
    void *              user_tag;
};

void set_reader_tag( ReaderContext * reader, void * tag )
{
    reader->user_tag = tag;
}

void * get_reader_tag( ReaderContext * reader )
{
    return reader->user_tag;
}

/*
 * main reading function what is used to get symbols from certain source -
 * file stream or string and calculate position as well
 */
static int context_getc( ReaderContext * reader_context )
{
    int c;

    if( reader_context->ungetc )
    {
        c = reader_context->ungetc;
        reader_context->ungetc = 0;
    }
    else
    {
        c = fgetc( reader_context->stream );

        if( c == '\n' )
        {
            ++ reader_context->line;
            reader_context->pos = 1;
        }
        else
            ++ reader_context->pos;
    }

    return c;
}


static bool isidstart_valid( int c )
{
    static int buf[256] = { 0 };
    static int initialized = 0;

    // initialize buffer
    if( !initialized )
    {
        int i;
        for( i = 0; i < 256; ++ i )
        {
            if(
                ((i >= 'a') && (i <= 'z')) ||
                ((i >= 'A') && (i <= 'Z')) ||
                ((i >= '0') && (i <= '9')) ||
                (i == '?') || (i == '!') || (i == '.') || (i == '%') ||
                (i == '$') || (i == '^') || (i == '_') ||
                (i == '<') || (i == '>') || (i == '=') || (i == '~') ||
                (i == '+') || (i == '-') || (i == '*') || (i == '/')
                )
            {
                buf[i] = 1;
            }
        }

        initialized = 1;
    }
    
    return (c > 0 && c < 256) ? buf[c] : false;
}

static bool isidbody_valid( int c )
{
    return isidstart_valid( c );
}

static void reader_error(
    ReaderContext * r, ErrResult err_result )
{
    r->last_error = err_result;
    longjmp( r->err_ret, 1 );
}

/*
 * skips whitespace with comments block
 * and returns first meaningful symbol
 */
static int read_skip_whitespace( ReaderContext * ctx )
{
    for( ;; )
    {
        int c = context_getc( ctx );

        if( c == ';' )
        {
            // skip single line comments
            do
            {
                c = context_getc( ctx );
            } while( c != '\n' && c > 0 );

            continue;
        }
        else if( c == '#' )
        {
            // reveal multiline comments
            int c2 = context_getc( ctx );
            if( c2 == '|' )
            {
                c = context_getc( ctx );
                c2 = context_getc( ctx );

                // skip multiline comments
                for( ;; )
                {
                    if( c == '|' && c2 == '#' )
                        break;

                    c = c2;
                    c2 = context_getc( ctx );
                    if( c2 <= 0 )
                        break;
                }

                continue;
            }
            else
            {
                // unget prev character and continue reading
                ctx->ungetc = c2;
            }
        }
        else if( c == ' ' || c == '\r' || c == '\n' || c == '\t' )
        {
            // skip whitespace symbols
            continue;
        }

        return c;
    }
}


static Obj *
parse_string( ReaderContext * r )
{
    char * str_buf = r->str_buf;
    size_t len = 0;

    for( ;; )
    {
        int c = context_getc( r );
        if( c <= 0 )
        {
            // closing `"' hadn't met
            reader_error( r, QL_ERR_NON_TERMINATED_STRING );
        }

        if( c == '"' )
        {
            break;
        }

        str_buf[len ++] = (char)c;
        if( len > r->max_str_size )
        {
            reader_error( r, QL_ERR_WRONG_CONF );
        }
    }

    return (Obj *)alloc_str_len( r->alloc_ctx, str_buf, len );
}

enum
{
    QL_DIGIT_STYLE_NONE     = 0,
    QL_DIGIT_STYLE_INTEGER  = 1,
    QL_DIGIT_STYLE_DOUBLE   = 2,
};

static int
is_equal_tokens( char * id_buf, size_t id_len, const char * token, size_t token_len )
{
    size_t      i;
    if( id_len != token_len )
    {
        return 0;
    }

    for( i = 0; i < id_len; ++ i )
    {
        if( id_buf[i] != token[i] )
        {
            return 0;
        }
    }

    return 1;
}

static Obj *
parse_atom( ReaderContext * r )
{
    int         floating_point = 0;
    int         c = context_getc( r );
    int         digit_style = QL_DIGIT_STYLE_INTEGER;
    Obj *       e = 0;
    Id *        namespace_spec = 0;
    int         is_global_symbol = 0;

    if( c == '"' )
    {
        return parse_string( r );
    }
    else if( c == ':' )
    {
        is_global_symbol = 1;
        c = context_getc( r );
    }

    if( isidstart_valid(c) )
    {
        char *  id_buf = r->str_buf;
        size_t  id_len = 0;

        do
        {
            if( id_len > r->max_str_size )
            {
                reader_error( r, QL_ERR_WRONG_CONF );
            }

            // digit test
            if( (digit_style != QL_DIGIT_STYLE_NONE) &&
                ((c < '0') || (c > '9')) )
            {
                if( c == '.' )
                {
                    if( (++ floating_point) > 1 )
                    {
                        // floating point used twice
                        reader_error( r, QL_ERR_MALFORMED_TOKEN );
                    }
                    digit_style = QL_DIGIT_STYLE_DOUBLE;
                }
                else
                {
                    digit_style = QL_DIGIT_STYLE_NONE;
                }
            }

            id_buf[id_len ++] = (char)c;
            c = context_getc( r );

            // check whether namespace delimeter exists
            if( c == ':' )
            {
                // check namespace sanity - it may not be digit
                if( digit_style != QL_DIGIT_STYLE_NONE || id_len == 0 )
                {
                    reader_error( r, QL_ERR_MALFORMED_TOKEN );
                }

                namespace_spec = alloc_id_len(
                    r->alloc_ctx,
                    id_buf,
                    id_len,
                    // in case of specified namespace the default one will not be considered
                    namespace_spec
                    );
                id_len = 0;
                
                // read one another symbol right now - it must be a symbol
                c = context_getc( r );
            }
        }
        while( isidbody_valid(c) );
        r->ungetc = c;

        if( 0 != namespace_spec )
        {
            // global symbols are supposed to be started from only one namespace qualifier (i.e. ':')
            // other namespace qualifiers are not expected
            if( is_global_symbol || (id_len == 0) )
            {
                reader_error( r, QL_ERR_MALFORMED_TOKEN );
            }

            e = (Obj *)alloc_id_len( r->alloc_ctx, id_buf, id_len, namespace_spec );
        }
        else if( is_equal_tokens(id_buf, id_len, "nil", 3) )
        {
            e = g_nil_obj;
        }
        else if( is_equal_tokens(id_buf, id_len, "t", 1) )
        {
            e = g_t_obj;
        }
        else if( digit_style == QL_DIGIT_STYLE_INTEGER )
        {
            id_buf[id_len] = 0; // atoi/atof can work with zero-terminated strings only
            e = (Obj *)alloc_int_num( r->alloc_ctx, atoi(id_buf) );
        }
        else if( digit_style == QL_DIGIT_STYLE_DOUBLE )
        {
            id_buf[id_len] = 0; // atoi/atof can work with zero-terminated strings only
            e = (Obj *)alloc_dbl_num( r->alloc_ctx, atof(id_buf) );
        }
        else
        {
            // no namespace specifiers - consider as
            // pretty ordinary token in default namespace if global namespace qualifiers was not specified
            e = (Obj *)alloc_id_len( r->alloc_ctx, id_buf, id_len, is_global_symbol ? 0 : r->default_ns );
        }
    }
    else if( 0 != is_global_symbol )
    {
        reader_error( r, QL_ERR_MALFORMED_TOKEN );
    }

    return e;
}

ErrResult reader_get_last_error( ReaderContext * reader )
{
    return reader->last_error;
}

static Obj *
parse_obj( ReaderContext * r )
{
    List *    list_stack = r->list_stack;

    // reading loop
    for( ;; )
    {
        int c = read_skip_whitespace( r );

        // eof check
        if( c <= 0 )
        {
            if( 0 != list_stack->car )
            {
                reader_error( r, QL_ERR_UNMATCHED_CLOSE_BRACE );
            }

            return 0;
        }

        // handle meaningful symbols
        switch( c )
        {
        case '(':
            list_push( r->alloc_ctx, list_stack, (Obj *)alloc_list(r->alloc_ctx) );
            break;

        case ')':
            // check whether this brace does not match any open brace
            if( 0 == list_stack->car )
            {
                reader_error( r, QL_ERR_UNMATCHED_OPEN_BRACE );
            }
            else
            {
                Obj *         top_list = list_stack->car;

                // if list is empty - replace it to nil
                Obj *         top_elem = (0 == ((List *)top_list)->car ? g_nil_obj : top_list);

                // list has at least two elements?
                if( 0 != list_stack->cdr )
                {
                    list_pop( r->alloc_ctx, list_stack );

                    // add this list to the top-level list
                    list_add( r->alloc_ctx, (List *)list_stack->car, top_elem );
                }
                else
                {
                    // this is a last element
                    return top_elem;
                }
            }
            break;

        default:
            // do parse identifiers/numbers/t/nil elements

            r->ungetc = c;
            {
                Obj *     elem = parse_atom( r );
                if( 0 != elem )
                {
                    if( 0 != list_stack->car )
                    {
                        list_add( r->alloc_ctx, (List *)list_stack->car, elem );
                        break;
                    }
                    else
                    {
                        return elem;
                    }
                }
            }

            // unknown symbol passed
            reader_error( r, QL_ERR_UNKNOWN_SYMBOL );
        } // switch
    } // for( ;; )

    assert( !"never should came here" );
    return 0;
}

struct ReaderContext * create_reader(
    AllocContext *      ac,
    FILE *              stream,
    const char *        stream_name
    )
{
    ReaderContext * r = ql_mallocz( ac, sizeof(ReaderContext) );
    
    r->alloc_ctx = ac;
    r->stream = stream;
    r->stream_name = stream_name;

    // perform initialization
    r->line = 1;
    r->max_str_size = QL_DEFAULT_MAX_STR_SIZE;
    r->str_buf = ql_malloc( r->alloc_ctx, r->max_str_size + 1 );

    return r;
}

void
set_reader_max_str_size( ReaderContext * r, size_t max_str_size )
{
    r->max_str_size = (max_str_size != 0 ? max_str_size : QL_DEFAULT_MAX_STR_SIZE);
    r->str_buf = ql_realloc( r->alloc_ctx, r->str_buf, r->max_str_size + 1 );
}

Obj * read_obj( ReaderContext * r )
{
    Obj *     result;

    r->last_error = QL_OK;

    if( setjmp(r->err_ret) != 0 )
    {
        free_list_chain( r->alloc_ctx, r->list_stack, 0, 0 );
        r->list_stack = 0;
        return 0;
    }

    assert( r->list_stack == 0 );
    r->list_stack = alloc_list( r->alloc_ctx );

    result = parse_obj( r );

    free_list_chain( r->alloc_ctx, r->list_stack, 0, 0 );
    r->list_stack = 0;
    return result;
}

Id * set_default_namespace( ReaderContext * r, Id * default_ns )
{
    Id * old = r->default_ns;
    
    r->default_ns = default_ns;
    
    return old;
}

Id * get_default_namespace( ReaderContext * r )
{
    return r->default_ns;
}

int reader_line( ReaderContext * r )
{
    return r->line;
}

int reader_pos( ReaderContext * r )
{
    return r->pos;
}

const char * reader_stream_name( ReaderContext * r )
{
    return r->stream_name;
}

void free_reader( ReaderContext * r )
{
    ql_free( r->str_buf );
    ql_free( r );
}
